package comms;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Vector;
import java.util.Enumeration;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;
import javax.microedition.io.ServerSocketConnection;

import ap.*;
import agent.*;

/**
 * A class which represents a connection which uses TCP sockets in order
 * to either send or receieve mobile agents.
 * <p>
 * Runs in a seperate Thread if listening for connections otherwise waits for
 * send or close methods to be called.
 * <p>
 * When in receiver mode; creates SocketAgentConnectionHandler objects to handle
 * incoming connection requests.
 * 
 * @author Kevin
 * @see AgentConnection
 */
public class SocketAgentConnection extends Thread implements AgentConnection {
	
	//The ConnectionManager which created this object.
	private ConnectionManager owner;
	
	//The current SocketConnection for this object.
	private SocketConnection currentConnection;
	
	//The current ServerSocketConnection for this object.
	private ServerSocketConnection currentServerConnection;
	
	//The SocketAgentConnectionHandler objects currently open.
	private Vector connectionHandlers;
	
	//The status of this connection as defined in the AgentConnection interface.
	private int connectionStatus;
	
	//Whether this object is listening for connections of not.
	private boolean listening;
	
	//The Input and Output Streams associated with this object.
	private InputStream inStream;
	private OutputStream outStream;

	/**
	 * Constructor for this class. Determines whether to create an object for
	 * sending or listening based on the length of the address parameter.
	 * <p>
	 * Starts the Thread if this object is a receiver/listener object.
	 * 
	 * @param manager The ConnectionManager which created this object.
	 * @param address The address to open a connection with or "" if a ServerSocketConnection.
	 * @param port The port for this connection.
	 */
	public SocketAgentConnection(ConnectionManager manager, String address, String port) {
		this.owner = manager;
		this.currentConnection = null;
		this.currentServerConnection = null;
		this.connectionHandlers = null;
		
		if (address.length() > 0) {
			this.connectionStatus = AgentConnection.SENDER_ONLY;
			this.listening = false;
			try {
				this.currentConnection = (SocketConnection)Connector.open("socket://" + address + ":" + port);
				this.currentConnection.setSocketOption(SocketConnection.LINGER, 5);
				this.inStream = this.currentConnection.openInputStream();
				this.outStream = this.currentConnection.openOutputStream();
			} catch (IOException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
		} else {
			this.connectionStatus = AgentConnection.RECEIVER_ONLY;
			this.listening = true;
			try {
				this.currentServerConnection = (ServerSocketConnection)Connector.open("socket://:" + port);
				this.inStream = null;
				this.outStream = null;
			} catch (IOException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
			
			this.start();
		}	
	}
	
	/**
	 * Run method for this Thread; constantly waits for incoming Socket connections until
	 * such time as the closeConnection method for this object is called.
	 * <p>
	 * Each incoming connection is passed to its own SocketAgentConnectionHandler object
	 * to ensure this Thread doesn't become blocked.
	 * 
	 */
	public void run() {
		this.connectionHandlers = new Vector();
		
		while (listening && ((this.connectionStatus == AgentConnection.RECEIVER_ONLY) || (this.connectionStatus == AgentConnection.SENDER_RECEIVER))) {
			try {
				this.currentConnection = (SocketConnection)this.currentServerConnection.acceptAndOpen();
				this.connectionHandlers.addElement(new SocketAgentConnectionHandler(this.owner, this.currentConnection));
			} catch (IOException e) {
				System.err.println(e.getMessage());
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Sends an agent via the current connection.
	 * 
	 * @param agent The agent to be sent.
	 * @return True if send successful; false otherwise.
	 * @throws AgentConnectionException If connection could not be opened.
	 */
	public boolean sendAgent(Agent agent) throws AgentConnectionException {
		boolean sent = false;
		
		if ((this.connectionStatus == AgentConnection.RECEIVER_ONLY) ||
				(this.connectionStatus == AgentConnection.CLOSED))
				throw new AgentConnectionException("Connection closed or not in send mode.");
		
		try {	
			this.outStream.write(agent.toString().getBytes());
			this.outStream.flush();
			sent = true;
		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		
		return sent;
	}

	/**
	 * Sends an agent in the form of an AgentPkt object via the current connection.
	 * 
	 * @param agentPack The AgentPkt for the agent concerned.
	 * @return True if send successful; false otherwise.
	 * @throws AgentConnectionException If connection could not be opened.
	 */
	public boolean sendAgent(AgentPkt agentPack) throws AgentConnectionException {
		boolean sent = false;
		
		if ((this.connectionStatus == AgentConnection.RECEIVER_ONLY) ||
				(this.connectionStatus == AgentConnection.CLOSED))
				throw new AgentConnectionException("Connection closed or not in send mode.");
		
		try {	
			this.outStream.write(agentPack.getAgentString().getBytes());
			this.outStream.flush();
			sent = true;
			this.outStream.close();
		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		
		return sent;
	}
	
	/**
	 * Attempts to close the current connection as soon as possible.
	 *
	 */
	public void closeConnection() {
		this.listening = false;
		this.connectionStatus = AgentConnection.CLOSED;
		
		try {
			if (this.inStream != null) this.inStream.close();
			if (this.outStream != null) this.outStream.close();
			if (this.currentServerConnection != null) this.currentServerConnection.close();
			if (this.currentConnection != null) this.currentConnection.close();
			if (this.connectionHandlers != null) {
				for (Enumeration e = this.connectionHandlers.elements(); e.hasMoreElements(); ) {
					((SocketAgentConnectionHandler)e.nextElement()).close();
				}
			}
		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}
	
	/**
	 * Returns the saved status value of the connection as indicated by the static final
	 * integer values already defined.
	 * 
	 * @return The status of the connection as indicated by the static constants above.
	 */
	public int getStatus() {
		return this.connectionStatus;
	}
 }
